use std::{
    fs,
    io::{prelude::*, BufRead, BufReader},
    net::{TcpListener, TcpStream},
};

#[derive(Debug, PartialEq, Eq)]
pub struct RequestLine {
    pub method: String,
    pub path: String,
    pub version: String,
}

pub struct RouteMatch {
    pub status_line: &'static str,
    pub filename: &'static str,
}

pub struct Response {
    pub status_line: String,
    pub headers: Vec<(String, String)>,
    pub body: String,
}

pub fn parse_request_line(line: &str) -> RequestLine {
    let mut parts = line.split_whitespace();
    let method = parts.next().unwrap_or_default().to_string();
    let path = parts.next().unwrap_or_default().to_string();
    let version = parts.next().unwrap_or_default().to_string();

    RequestLine {
        method,
        path,
        version,
    }
}

pub fn route_request(request: &RequestLine) -> RouteMatch {
    match request {
        RequestLine { method, path, .. } if method == "GET" && path == "/" => RouteMatch {
            status_line: "HTTP/1.1 200 OK",
            filename: "hello.html",
        },
        _ => RouteMatch {
            status_line: "HTTP/1.1 404 NOT FOUND",
            filename: "404.html",
        },
    }
}

pub fn build_response(response: Response) -> String {
    let mut http = response.status_line;
    http.push_str("\r\n");

    for (key, value) in response.headers {
        http.push_str(&format!("{key}: {value}\r\n"));
    }

    http.push_str("\r\n");
    http.push_str(&response.body);

    http
}

pub fn handle_connection(mut stream: TcpStream) -> std::io::Result<()> {
    let mut buf_reader = BufReader::new(&mut stream);
    let mut request_line = String::new();
    buf_reader.read_line(&mut request_line)?;

    let request_line = request_line.trim_end();
    let parsed = parse_request_line(request_line);
    let route = route_request(&parsed);

    let contents = fs::read_to_string(route.filename)?;
    let length = contents.len();

    let response = build_response(Response {
        status_line: route.status_line.to_string(),
        headers: vec![("Content-Length".into(), length.to_string())],
        body: contents,
    });

    stream.write_all(response.as_bytes())?;
    Ok(())
}

pub fn run_server() -> std::io::Result<()> {
    let listener = TcpListener::bind("127.0.0.1:7878")?;

    for stream in listener.incoming() {
        let stream = stream?;
        handle_connection(stream)?;
    }

    Ok(())
}
